package com.jusha.formulacalculation;


import com.jusha.formulacalculation.enums.ResultStatus;
import com.jusha.formulacalculation.exception.EvalException;
import com.jusha.formulacalculation.exception.FunctionParamException;
import com.jusha.formulacalculation.exception.NullFunctionException;
import com.jusha.formulacalculation.functions.ManageFunctions;
import com.jusha.formulacalculation.functions.PackFunction;
import com.jusha.formulacalculation.interfaces.IProcessFunction;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 表达式计算执行类
 */
public class Eval {

    // Token state enums
    private enum State {
        NONE,
        /**
         * 操作数
         */
        OPERAND,
        /**
         * 操作符
         */
        OPERATOR,
        /**
         * 函数
         */
        FUNCTION,
        /**
         * 常量
         */
        SYMBOL
    }
    /**
     * 标准字符，公式中出现的字符范围，不允许出现标准外字符
     * 标准字符 a-z A-Z 0-9 . + - * /
     */
    private final static String STANDARD_CHARS_REGEX="[,.0-9a-zA-Z\\(\\)+\\-*/]+";
    // 错误信息
    private final static String ErrExpressionContent="公式内容格式异常";
    private final static String ErrInvalidOperand = "无效的操作数";
    private final static String ErrOperandExpected = "应为操作数";
    private final static String ErrOperatorExpected = "应为操作符";
    private final static String ErrUnmatchedClosingParen = "左右括号不匹配";
    private final static String ErrMultipleDecimalPoints = "包含多个小数点的操作数";
    private final static String ErrUnexpectedCharacter = "遇到意想不到的问题 \"%s\"";
    private final static String ErrUndefinedFunction = "未定义的函数 \"%s\"";
    private final static String ErrResultError = "函数计算结果不合业务规则 \"%s\"";
    private final static String ErrClosingParenExpected = "需要右括号闭合";
    private final static String ErrWrongParamCount = "错误的函数参数";
    private final static String ErrFunctionExpected = "函数应跟在操作符后或为另一个函数的参数";

    /**
     * 公式清洗
     *
     * @param expression
     * @return
     */
    public String cleanExpression(String expression) throws EvalException {
        //公式不区分大小写，转为纯小写处理
        expression = expression.toLowerCase();

        expression = expression
                .replaceAll("，", ",")
                .replaceAll("（", "(")
                .replaceAll("）", ")")
                .replaceAll("\\s", "");

        if(!expression.matches(STANDARD_CHARS_REGEX))
        {
            throw new EvalException(ErrExpressionContent,-1);
        }
        return expression;
    }

    /**
     * 计算公式,并返回结果
     *
     * @param expression 公式
     * @return
     * @throws EvalException
     */
    public double Execute(String expression) throws EvalException {
        //清洗
        expression = cleanExpression(expression);
        //转后缀表达式
        List<String> exp = TokenizeExpression(expression);
        //计算
        return ExecuteTokens(exp);
    }

    /**
     * 标准中缀表达式解析为后缀表达式
     *
     * @param expression
     * @return
     * @throws EvalException
     */
    private List<String> TokenizeExpression(String expression) throws EvalException {
        List<String> tokens = new ArrayList<>();
        Stack<String> stack = new Stack<>();
        State state = State.NONE;
        int parenCount = 0;
        String temp;

        TextParser parser = new TextParser(expression);

        while (!parser.endOfText()) {
            if (Character.isWhitespace(parser.Peek())) {
                // Ignore spaces, tabs, etc.
            }
            //左括号,直接入临时栈
            else if (parser.Peek() == '(') {
                // Cannot follow operand
                if (state == State.OPERAND)
                    throw new EvalException(ErrOperatorExpected, parser.getPos());
                // Allow additional unary operators after "("
                // push opening parenthesis onto stack
                stack.push(String.valueOf(parser.Peek()));
                // Track number of parentheses
                parenCount++;
            }
            //右括号,临时栈出栈直到第一个左括号,出栈内容顺序放入结果集
            else if (parser.Peek() == ')') {
                // Must follow operand
                if (state != State.OPERAND)
                    throw new EvalException(ErrOperandExpected, parser.getPos());
                // Must have matching open parenthesis
                if (parenCount == 0)
                    throw new EvalException(ErrUnmatchedClosingParen, parser.getPos());
                // pop all operators until matching "(" found
                temp = stack.pop();
                while (!"(".equals(temp)) {
                    tokens.add(temp);
                    temp = stack.pop();
                }
                // Track number of parentheses
                parenCount--;
            }
            //运算符,优先级更高的运算符入临时栈,优先级等于或低于临时栈栈顶元素时临时栈栈顶元素出栈放入结果集
            else if ("+-*/^".contains(String.valueOf(parser.Peek()))) {
                // Need a bit of extra code to support unary operators
                if (state == State.OPERAND) {
                    // pop operators with precedence >= current operator
                    int currPrecedence = GetPrecedence(String.valueOf(parser.Peek()));
                    //当前操作符优先级低于或等于临时栈栈顶操作符优先级时,临时栈栈顶元素出栈
                    while (stack.size() > 0 && GetPrecedence(stack.peek()) >= currPrecedence)
                        tokens.add(stack.pop());
                    stack.push(String.valueOf(parser.Peek()));
                    state = State.OPERATOR;
                } else {
                    throw new EvalException(ErrOperandExpected, parser.getPos());
                }
            } else if (Character.isDigit(parser.Peek()) || parser.Peek() == '.') {
                if (state == State.OPERAND) {
                    // Cannot follow other operand
                    throw new EvalException(ErrOperatorExpected, parser.getPos());
                }
                // Parse number
                temp = ParseNumberToken(parser);
                tokens.add(temp);
                state = State.OPERAND;
                continue;
            }
            //逗号表示函数参数,应跟在函数后
            else if (parser.Peek() == ',') {
                if (!stack.peek().matches("[a-z_]+"))
                    throw new EvalException(ErrWrongParamCount, parser.getPos());
                state = State.FUNCTION;
            } else {
                double result;

                // 处理函数和常量
                if (state == State.OPERAND) {
                    // Symbol or function cannot follow other operand
                    throw new EvalException(ErrOperatorExpected, parser.getPos());
                }
                if (!(Character.isLetter(parser.Peek()) || parser.Peek() == '_')) {
                    // Invalid character
                    temp = String.format(ErrUnexpectedCharacter, parser.Peek());
                    throw new EvalException(temp, parser.getPos());
                }
                // Save start of symbol for error reporting
                int symbolPos = parser.getPos();
                // Parse this symbol
                temp = ParseSymbolToken(parser);
                // Skip whitespace
                parser.MovePastWhitespace();
                //函数入栈

                // Check for parameter list
                if (parser.Peek() == '(') {
                    //带括号,表示有参,是函数
                    //函数必须跟在操作符或函数后
                    if (state != State.OPERATOR && state != State.FUNCTION)
                        throw new EvalException(ErrFunctionExpected, parser.getPos());
                    //函数入栈时应先入栈函数的左括号,再入栈函数
                    if (!ManageFunctions.has(temp)) {
                        throw new EvalException(ErrUndefinedFunction, parser.getPos());
                    }
                    stack.push(String.valueOf(parser.Peek()));
                    parenCount++;
                    parser.MoveAhead();
                    stack.push(temp);
                    state = State.FUNCTION;

                    // Found parameter list, evaluate function
                    //查找参数列表，执行函数
//                    result = EvaluateFunction(parser, temp, symbolPos);
                } else {
                    //无括号,表示无参,是常量,比函数优先级更高
                    //无参数，直接获取符号或变量值
                    if (state != State.OPERATOR && state != State.FUNCTION)
                        throw new EvalException(ErrFunctionExpected, parser.getPos());
                    stack.push(temp);
                    // No parameter list, evaluate symbol (variable)
//                    result = EvaluateSymbol(temp, symbolPos);

                    state = State.SYMBOL;
                }
                continue;
            }
            parser.MoveAhead();
        }
        // Expression cannot end with operator
        if (state == State.OPERATOR)
            throw new EvalException(ErrOperandExpected, parser.getPos());
        // Check for balanced parentheses
        if (parenCount > 0)
            throw new EvalException(ErrClosingParenExpected, parser.getPos());
        // Retrieve remaining operators from stack
        while (stack.size() > 0)
            tokens.add(stack.pop());
        return tokens;
    }

    /// <summary>
    /// Parses and extracts a numeric value at the current position
    /// </summary>
    /// <param name="parser">TextParser object</param>
    /// <returns></returns>
    private String ParseNumberToken(TextParser parser) throws EvalException {
        boolean hasDecimal = false;
        int start = parser.getPos();
        while (Character.isDigit(parser.Peek()) || parser.Peek() == '.') {
            if (parser.Peek() == '.') {
                if (hasDecimal)
                    throw new EvalException(ErrMultipleDecimalPoints, parser.getPos());
                hasDecimal = true;
            }
            parser.MoveAhead();
        }
        // Extract token
        String token = parser.Extract(start, parser.getPos());
        if (".".equals(token))
            throw new EvalException(ErrInvalidOperand, parser.getPos() - 1);
        return token;
    }

    /// <summary>
    /// Parses and extracts a symbol at the current position
    /// </summary>
    /// <param name="parser">TextParser object</param>
    /// <returns></returns>
    protected String ParseSymbolToken(TextParser parser) {
        int start = parser.getPos();
        while (Character.isLetterOrDigit(parser.Peek()) || parser.Peek() == '_')
            parser.MoveAhead();
        return parser.Extract(start, parser.getPos());
    }

    /// <summary>
    /// Evaluates a function and returns its value. It is assumed the current
    /// position is at the opening parenthesis of the argument list.
    /// </summary>
    /// <param name="parser">TextParser object</param>
    /// <param name="name">Name of function</param>
    /// <param name="pos">getPos() at start of function</param>
    /// <returns></returns>
    protected double EvaluateFunction(TextParser parser, String name, int pos) {
        double result = 0;

//        // Parse function parameters
//        List<Double> parameters = ParseParameters(parser);
//
//        // We found a function reference
//        FunctionStatus status = FunctionStatus.UndefinedFunction;
//        if (ProcessFunction != null) {
//            FunctionEventArgs args = new FunctionEventArgs();
//            args.Name = name;
//            args.Parameters = parameters;
//            args.Result = result;
//            args.Status = FunctionStatus.OK;
//            ProcessFunction(this, args);
//            result = args.Result;
//            status = args.Status;
//        }
//        if (status == FunctionStatus.UndefinedFunction)
//            throw new EvalException(String.Format(ErrUndefinedFunction, name), pos);
//        if (status == FunctionStatus.WrongParameterCount)
//            throw new EvalException(ErrWrongParamCount, pos);
//        if (status == FunctionStatus.ResultError)
//            throw new EvalException(ErrResultError, pos);
        return result;
    }

    /// <summary>
    /// Evaluates each parameter of a function's parameter list and returns
    /// a list of those values. An empty list is returned if no parameters
    /// were found. It is assumed the current position is at the opening
    /// parenthesis of the argument list.
    /// </summary>
    /// <param name="parser">TextParser object</param>
    /// <returns></returns>
    protected List<Double> ParseParameters(TextParser parser) throws EvalException {
        // Move past open parenthesis
        parser.MoveAhead();

        // Look for function parameters
        List<Double> parameters = new ArrayList<Double>();
        parser.MovePastWhitespace();
        if (parser.Peek() != ')') {
            // Parse function parameter list
            int paramStart = parser.getPos();
            int parenCount = 1;

            while (!parser.endOfText()) {
                if (parser.Peek() == ',') {
                    // Note: Ignore commas inside parentheses. They could be
                    // from a parameter list for a function inside the parameters
                    if (parenCount == 1) {
                        parameters.add(EvaluateParameter(parser, paramStart));
                        paramStart = parser.getPos() + 1;
                    }
                }
                if (parser.Peek() == ')') {
                    parenCount--;
                    if (parenCount == 0) {
                        parameters.add(EvaluateParameter(parser, paramStart));
                        break;
                    }
                } else if (parser.Peek() == '(') {
                    parenCount++;
                }
                parser.MoveAhead();
            }
        }
        // Make sure we found a closing parenthesis
        if (parser.Peek() != ')')
            throw new EvalException(ErrClosingParenExpected, parser.getPos());
        // Move past closing parenthesis
        parser.MoveAhead();
        // Return parameter list
        return parameters;
    }

    /// <summary>
    /// Extracts and evaluates a function parameter and returns its value. If an
    /// exception occurs, it is caught and the column is adjusted to reflect the
    /// position in original String, and the exception is rethrown.
    /// </summary>
    /// <param name="parser">TextParser object</param>
    /// <param name="paramStart">Column where this parameter started</param>
    /// <returns></returns>
    protected double EvaluateParameter(TextParser parser, int paramStart) throws EvalException {
        try {
            // Extract expression and evaluate it
            String expression = parser.Extract(paramStart, parser.getPos());
            return Execute(expression);
        } catch (EvalException ex) {
            // Adjust column and rethrow exception
            ex.setColumn(ex.getColumn() + paramStart);
            throw ex;
        }
    }

    /**
     * 返回
     *
     * @param name
     * @param pos
     * @return
     */
    /// <summary>
    /// This method evaluates a symbol name and returns its value.
    /// </summary>
    /// <param name="name">Name of symbol</param>
    /// <param name="pos">getPos() at start of symbol</param>
    /// <returns></returns>
    protected double EvaluateSymbol(String name, int pos) {
        double result = 0;

//         We found a symbol reference
//        SymbolStatus status = SymbolStatus.UNDEFINED_SYMBOL;
//        if (ProcessSymbol != null) {
//            SymbolEventArgs args = new SymbolEventArgs();
//            args.Name = name;
//            args.Result = result;
//            args.Status = SymbolStatus.OK;
//            ProcessSymbol(this, args);
//            result = args.Result;
//            status = args.Status;
//        }
//        if (status == SymbolStatus.UndefinedSymbol)
//            throw new EvalException(String.Format(ErrUndefinedSymbol, name), pos);
        return result;
    }

    /// <summary>
    /// Evaluates the given list of tokens and returns the result.
    /// Tokens must appear in postfix order.
    /// </summary>
    /// <param name="tokens">List of tokens to evaluate.</param>
    /// <returns></returns>
    protected double ExecuteTokens(List<String> tokens) throws EvalException {
        Stack<Double> stack = new Stack<Double>();
        double tmp, tmp2;
        for (String token : tokens) {
            // Is this a value token?
            //int count = token.Count(c => Char.IsDigit(c) || c == '.');
            //if (count == token.Length)
            Double dToken = null;
            try {
                dToken = Double.parseDouble(token);
            } catch (Exception e) {
            }
            if (dToken != null) {
                stack.push(dToken);
            } else if ("+".equals(token)) {
                stack.push(stack.pop() + stack.pop());
            } else if ("-".equals(token)) {
                tmp = stack.pop();
                tmp2 = stack.pop();
                stack.push(tmp2 - tmp);
            } else if ("*".equals(token)) {
                stack.push(stack.pop() * stack.pop());
            } else if ("/".equals(token)) {
                tmp = stack.pop();
                tmp2 = stack.pop();
                stack.push(tmp2 / tmp);
            } else if ("^".equals(token)) {
                tmp = stack.pop();
                tmp2 = stack.pop();
                stack.push(Math.pow(tmp2, tmp));
            } else if (token.matches("[a-z_]+")) {
                //函数或者常量,函数和常量规则上不做区分,实际上常量即为特殊的0参函数,只是公式写法上允许不带空括号()
                try {
                    PackFunction function = ManageFunctions.get(token);
                    //填充参数
                    int paramsCount = function.getParamsCount();
                    double[] params = new double[paramsCount];
                    while (paramsCount-- > 0) {
                        params[paramsCount] = stack.pop();
                    }
                    Result result = function.execute(params);
                    if (result.getStatus() == ResultStatus.OK) {
                        stack.push(result.getValue());
                    } else {
                        throw new EvalException("函数执行失败", -1);
                    }
                } catch (NullFunctionException e) {
                    throw new EvalException(ErrUndefinedFunction, -1);
                }
                catch (FunctionParamException e){
                    throw new EvalException(e.getMessage(), -1);
                }
                try {
                    IProcessFunction iProcessFunction = ManageFunctions.get(token);
                } catch (NullFunctionException e) {
                    e.printStackTrace();
                }
            }
        }
        // Remaining item on stack contains result
        return (stack.size() > 0) ? stack.pop() : 0.0;
    }

    /**
     * 返回操作符的优先级,数值越大优先级越高
     *
     * @param s
     * @return
     */
    protected int GetPrecedence(String s) {
        switch (s) {
            case "+":
            case "-":
                return 1;
            case "*":
            case "/":
                return 2;
            case "^":
                return 3;
        }
        return 0;
    }
}
